package com.za.plugin.transfer.form.insertupdate;


import cn.hutool.core.collection.CollUtil;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.za.plugin.pojo.Content;
import com.za.plugin.pojo.PredixStrAndCount;
import com.za.plugin.util.SqlUtil;
import com.za.plugin.util.StrUtil;
import org.apache.ibatis.mapping.ParameterMapping;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class InsertStyleTransfer implements StyleTransfer {

    @Override
    public boolean isSupport(String sql) {
        return sql.trim().startsWith("insert") && !sql.contains("on duplicate key update");
    }

    @Override
    public String transfer(String sql, String tableName
            , List<String> autoIncrProperties, List<ParameterMapping> parameterMappingsCopy,
                           List<ParameterMapping> parameterMappings, Set<String> pKs
            , Map<String, List<String>> pkAndUniqueKeys) {
        MySqlStatementParser parser = new MySqlStatementParser(sql);
        SQLStatement sqlStatement = parser.parseStatement();
        int batch = 100;
        if (sqlStatement instanceof MySqlInsertStatement) {
            List<SQLInsertStatement.ValuesClause> valuesList = ((MySqlInsertStatement) sqlStatement).getValuesList();
            int size = valuesList.size();
            int count = size / batch;
            int other = size % batch;
            Pattern compile = Pattern.compile("insert into.*?values\\s*\\(");
            Matcher matcher = compile.matcher(sql);
            if (matcher.find() && size > batch) {
                String group = matcher.group().substring(0, matcher.group().length() - 1);
                System.out.println(SQLUtils.toMySqlString(((MySqlInsertStatement) sqlStatement).getValuesList().get(0)));
                StringBuilder sb = new StringBuilder();
                if (count > 0) {
                    for (int j = 0; j < count; j++) {
                        sb.append(group);
                        for (int i = 0; i < batch; i++) {
                            sb.append(SQLUtils.toMySqlString(((MySqlInsertStatement) sqlStatement).getValuesList().get(0))).append(",");
                        }
                        sb.deleteCharAt(sb.length() - 1);
                        sb.append(";");
                    }
                }
                if (other > 0) {
                    sb.append(group);
                    for (int i = 0; i < other; i++) {
                        sb.append(SQLUtils.toMySqlString(((MySqlInsertStatement) sqlStatement).getValuesList().get(0))).append(",");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append(";");
                }
                sql = sb.toString();
            }
        }

        List<String> keys = SqlUtil.getKeysFromParameterMappingsCopy(parameterMappingsCopy);
        Map<String, ParameterMapping> map = SqlUtil.getPropertyMapFromParameterMappings(parameterMappingsCopy);
        Map<String, ParameterMapping> sqlMap = initSqlMap(sql, parameterMappingsCopy);
        boolean isForEach = SqlUtil.isForEach(parameterMappingsCopy);
        if (isForEach && sql.contains("insert into")) {
            MySqlStatementParser statementParser = new MySqlStatementParser(sql);
            List<SQLStatement> sqlStatements = statementParser.parseStatementList();
            if (sqlStatements.size() > 1) {
                int size = ((MySqlInsertStatement) sqlStatements.get(0)).getValues().getValues().size();
                List<SQLExpr> columns = ((MySqlInsertStatement) sqlStatements.get(0)).getColumns();
                List<String> columnList = SqlUtil.ExprToString(columns);
                List<Integer> autoIncrIdx = SqlUtil.findAutoIncrIdx(columnList, autoIncrProperties);
                StringBuilder sb = new StringBuilder();
                if (!CollUtil.isEmpty(autoIncrIdx)) {
                    for (SQLStatement statement : sqlStatements) {
                        statement.accept(new MySqlASTVisitorAdapter() {
                            @Override
                            public boolean visit(MySqlInsertStatement expr) {
                                Iterator<SQLExpr> iterator = expr.getColumns().iterator();
                                List<SQLInsertStatement.ValuesClause> valuesList = expr.getValuesList();
                                int start = 0;
                                while (iterator.hasNext()) {
                                    SQLExpr next = iterator.next();
                                    if (autoIncrIdx.contains(start)) {
                                        iterator.remove();
                                    }
                                    start++;
                                }

                                for (SQLInsertStatement.ValuesClause valuesClause : valuesList) {
                                    start = 0;
                                    Iterator<SQLExpr> iterator1 = valuesClause.getValues().iterator();
                                    while (iterator1.hasNext()) {
                                        SQLExpr next = iterator1.next();
                                        if (autoIncrIdx.contains(start)) {
                                            iterator1.remove();
                                        }
                                        start++;
                                    }
                                }
                                return super.visit(expr);
                            }
                        });
                        sb.append(SQLUtils.toMySqlString(statement)).append(";");

                    }
                    sql = sb.toString();
                    // 去除指定位置的自增列
                    ArrayList<Integer> clears = new ArrayList<>();
                    for (Integer incrIdx : autoIncrIdx) {
                        while (incrIdx < parameterMappingsCopy.size()) {
                            clears.add(incrIdx);
                            incrIdx += size;
                        }
                    }

                    parameterMappings.addAll(parameterMappingsCopy);
                    Iterator<ParameterMapping> iterator = parameterMappings.iterator();
                    int start = 0;
                    while (iterator.hasNext()) {
                        iterator.next();
                        if (clears.contains(start)) {
                            iterator.remove();
                        }
                        start++;
                    }
                } else {
                    parameterMappings.addAll(parameterMappingsCopy);
                }
            } else {
                sql = "INSERT INTO " + SqlUtil.getTableName(tableName) + getBatchSqlProperties(keys, map,
                        parameterMappings, pKs, sql, parameterMappingsCopy, autoIncrProperties);
            }
            return sql;
        }
        if (!isForEach && (sql.contains("insert into")) || sql.contains("insert ignore into")) {

            // 获取 主键字符串，一般主键都只有一个属性，也可能会是联合主键
            StringBuilder sb = new StringBuilder();
            if (!pKs.isEmpty()) {
                for (String pk : pKs) {
                    sb.append(pk).append(",");
                }
                sb.deleteCharAt(sb.length() - 1);
            }
            if (sql.contains("insert ignore into")) {
                // 添加 /*+ IGNORE_ROW_ON_DUPKEY_INDEX(t(id))*/ 解决 insert ignore into 问题
                sql = "INSERT " + "/*+ IGNORE_ROW_ON_DUPKEY_INDEX(" + tableName + "(" + sb.toString() + ")) */ "
                        + " INTO " + SqlUtil.getTableName(tableName) + getSqlProperties(map,
                        parameterMappings, pKs, sql, sqlMap, autoIncrProperties, parameterMappingsCopy);
            } else {
                // insert into (a,b,c) select a,b,c from tbl,没有输入参数
                if (!(sql.contains(" select ") || sql.contains(")select ")) && sql.contains("values")) {
                    sql = "INSERT INTO " + SqlUtil.getTableName(tableName) + getSqlProperties(map,
                            parameterMappings, pKs, sql, sqlMap, autoIncrProperties, parameterMappingsCopy);
                }
            }
        }

        return sql;
    }





    private Map<String, ParameterMapping> initSqlMap(String sql, List<ParameterMapping> parameterMappingsCopy) {
        Map<String, ParameterMapping> sqlMap = new LinkedHashMap<>();
        //获取表中的自增键，所有自增键插入前加上 “set IDENTITY_INSERT tblName ON”
        // 根据 insert into 的特点获取 sqlMap
        int idx = sql.indexOf("(");
        Content expectContent = StrUtil.getExpectContent(sql, idx);
        String expectStr = expectContent.getExpectStr();
        String[] propertyArr = expectStr.split(",");
        if (propertyArr.length == parameterMappingsCopy.size()) {
            int j = 0;
            for (String s : propertyArr) {
                sqlMap.put(s.trim(), parameterMappingsCopy.get(j++));
            }
        }
        return sqlMap;
    }


    private String getSqlProperties(Map<String, ParameterMapping> map
            , List<ParameterMapping> parameterMappings, Set<String> pKs, String sql,
                                    Map<String, ParameterMapping> sqlMap
            , List<String> autoIncrProperties, List<ParameterMapping> parameterMappingsCopy) {
        StringBuilder sb = new StringBuilder("( ");
        List<String> keys = SqlUtil.getKeyName2(sql);
        Map<String, String> functionValMap = SqlUtil.getFunctionValueBatch(sql, autoIncrProperties);
        for (String key : keys) {
            if (autoIncrProperties.contains(key)) {
                continue;
            }
            if (SqlUtil.KEY_WORD_SET.contains(key)) {
                sb.append("\"").append(StrUtil.toSqlProperty(key).toUpperCase()).append("\"").append(",");
            } else {
                sb.append(StrUtil.toSqlProperty(key)).append(",");
            }

        }
        sb.deleteCharAt(sb.length() - 1).append(")");
        sb.append("  values (");
        int j = -1;
        for (String key : keys) {
            if (autoIncrProperties.contains(key)) {
                j++;
                continue;
            }
            if (functionValMap.containsKey(key)) {
                sb.append(functionValMap.get(key)).append(",");
            } else {
                if (map.containsKey(StrUtil.toJavaProperty(key))) {
                    j++;
                    parameterMappings.add(map.get(StrUtil.toJavaProperty(key)));
                } else if (sqlMap.containsKey(key)) {
                    parameterMappings.add(sqlMap.get(key));
                    j++;
                } else {
                    j++;
                    parameterMappings.add(parameterMappingsCopy.get(j));
                    System.err.println(key + "没有被设置上,属性为空！！！！");
                }
                sb.append("?").append(",");
            }
        }
        return sb.deleteCharAt(sb.length() - 1).append(")").toString();
    }

    public String getBatchSqlProperties(List<String> keys, Map<String, ParameterMapping> map,
                                        List<ParameterMapping> parameterMappings, Set<String> pKs,
                                        String sql,
                                        List<ParameterMapping> parameterMappingsCopy, List<String> autoIncrProperties) {
        StringBuilder sb = new StringBuilder(" ( ");
        List<String> newKeys = SqlUtil.getKeyName2(sql);
        Map<String, String> functionValMap = SqlUtil.getFunctionValueBatch(sql, autoIncrProperties);

        PredixStrAndCount predixStrAndCount = StrUtil.getPredixStrAndCount(keys);
        int count = predixStrAndCount.getCount();
        String iteratorPrefix = predixStrAndCount.getIteratorPrefix();

        Map<String, ParameterMapping> sqlMap = new LinkedHashMap<>();
        int idx = sql.indexOf("(");
        Content expectContent = StrUtil.getExpectContent(sql, idx);
        assert expectContent != null;
        String expectStr = expectContent.getExpectStr();
        List<String> propertyArr = Arrays.stream(expectStr.split(","))
                .map(String::trim).collect(Collectors.toList());

        int len = (int) Math.ceil(((double) parameterMappingsCopy.size()) / propertyArr.size());
        int j = 0;
        for (int i = 0; i < len; i++) {
            for (String key : propertyArr) {
                if (functionValMap.containsKey(key)) {
                    continue;
                }
                // 特殊情况考虑
                sqlMap.put(iteratorPrefix + i + "." + key, parameterMappingsCopy.get(j));
                sqlMap.put(iteratorPrefix + i, parameterMappingsCopy.get(j));
                j++;
            }
        }

        for (String key : newKeys) {
            if (autoIncrProperties.contains(key)) {
                continue;
            }
            sb.append(StrUtil.toSqlProperty(key)).append(",");
        }
        sb.deleteCharAt(sb.length() - 1).append(")").append("  values (");

        j = 0;
        for (int i = 0; i < count; i++) {
            for (String key : newKeys) { // 不能对自增列进行插入
                if (autoIncrProperties.contains(key)) {
                    j++;
                    continue;
                }
                if (functionValMap.containsKey(key)) {
                    sb.append(functionValMap.get(key)).append(",");
                } else {
                    // insert List<String> 就只有 __frch_item_0 这种
//                    if (map.containsKey(iteratorPrefix + i)) {
//                        parameterMappings.add(map.get(iteratorPrefix + i));
//                    } else if (map.containsKey(iteratorPrefix + i + "." + StrUtil.toJavaProperty(key))) {
//                        // insert List<JavaPo> 有 __frch_item_0.name
//                        parameterMappings.add(map.get(iteratorPrefix + i + "." + StrUtil.toJavaProperty(key)));
//                    } else if (sqlMap.containsKey(iteratorPrefix + i + "." + StrUtil.toSqlProperty(key))) {
//                        parameterMappings.add(sqlMap.get(iteratorPrefix + i + "." + StrUtil.toSqlProperty(key)));
//                    } else if (sqlMap.containsKey(iteratorPrefix + i)) {
//                        parameterMappings.add(sqlMap.get(iteratorPrefix + i));
//                    } else {
//                        System.err.println("batch insert 可能找不到属性：" + iteratorPrefix + i + "." + StrUtil.toJavaProperty(key) + " ！！！");
//                        parameterMappings.add(parameterMappingsCopy.get(j));
//                    }
                    parameterMappings.add(parameterMappingsCopy.get(j));
                    sb.append("?").append(",");
                }
                j++;
            }
            if (i < count - 1) {
                sb.deleteCharAt(sb.length() - 1);
                sb.append(" ), ( ");
            }
        }
        return sb.deleteCharAt(sb.length() - 1).append(")").toString();
    }

}
